// Copyright 2022 jeff.li. and/or its affiliates.

#pragma once

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <functional>
#include <limits>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include <turbo/platform/port.h>


/* Define a convenience macro to test when address sanitizer is being used
 * across the different compilers (e.g. clang, gcc) */
#if TURBO_COMPILER_HAS_FEATURE(address_sanitizer) || __SANITIZE_ADDRESS__
#define TBIR_SANITIZE_ADDRESS 1
#endif

/*! \brief helper macro to supress Undefined Behavior Sanitizer for a specific function */
#if defined(__clang__)
#define TBIR_SUPPRESS_UBSAN __attribute__((no_sanitize("undefined")))
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 409)
#define TBIR_SUPPRESS_UBSAN __attribute__((no_sanitize_undefined))
#else
#define TBIR_SUPPRESS_UBSAN
#endif

// __ubsan_xxx is copy from pytorch
// https://github.com/pytorch/pytorch/blob/release/1.11/c10/macros/Macros.h
#if defined(__clang__)
#define __ubsan_ignore_float_divide_by_zero__ __attribute__((no_sanitize("float-divide-by-zero")))
#define __ubsan_ignore_undefined__ __attribute__((no_sanitize("undefined")))
#define __ubsan_ignore_signed_int_overflow__ __attribute__((no_sanitize("signed-integer-overflow")))
#define __ubsan_ignore_function__ __attribute__((no_sanitize("function")))
#else
#define __ubsan_ignore_float_divide_by_zero__
#define __ubsan_ignore_undefined__
#define __ubsan_ignore_signed_int_overflow__
#define __ubsan_ignore_function__
#endif

#ifdef __clang__
#define _TBIR_PRAGMA__(string) _Pragma(#string)
#define _TBIR_PRAGMA_(string) _TBIR_PRAGMA__(string)
#define TBIR_CLANG_DIAGNOSTIC_PUSH() _Pragma("clang diagnostic push")
#define TBIR_CLANG_DIAGNOSTIC_POP() _Pragma("clang diagnostic pop")
#define TBIR_CLANG_DIAGNOSTIC_IGNORE(flag) _TBIR_PRAGMA_(clang diagnostic ignored flag)
#define TBIR_CLANG_HAS_WARNING(flag) __has_warning(flag)
#else
#define TBIR_CLANG_DIAGNOSTIC_PUSH()
#define TBIR_CLANG_DIAGNOSTIC_POP()
#define TBIR_CLANG_DIAGNOSTIC_IGNORE(flag)
#define TBIR_CLANG_HAS_WARNING(flag) 0
#endif

#ifdef TBIR_HAVE_SHADOW_LOCAL_WARNINGS
#define TBIR_GCC_DISABLE_NEW_SHADOW_WARNINGS            \
  TBIR_GNU_DISABLE_WARNING("-Wshadow-compatible-local") \
  TBIR_GNU_DISABLE_WARNING("-Wshadow-local")            \
  TBIR_GNU_DISABLE_WARNING("-Wshadow")
#else
#define TBIR_GCC_DISABLE_NEW_SHADOW_WARNINGS /* empty */
#endif


// TBIR_ASSERT()
//
// In C++11, `assert` can't be used portably within constexpr functions.
// TBIR_ASSERT functions as a runtime assert but works in C++11 constexpr
// functions.  Example:
//
// constexpr double Divide(double a, double b) {
//   return TBIR_ASSERT(b != 0), a / b;
// }
//
// This macro is inspired by
// https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/
#if defined(NDEBUG)
#define TBIR_ASSERT(expr) (false ? static_cast<void>(expr) : static_cast<void>(0))
#else
#define TBIR_ASSERT(expr) \
  (TURBO_LIKELY((expr)) ? static_cast<void>(0) : [] { assert(false && #expr); }())
#endif

/*!
 * \brief whether throw ::tbir::runtime::Error instead of
 *  directly calling abort when FATAL error occurred
 *  NOTE: this may still not be perfect.
 *  do not use FATAL and CHECK in destructors
 */
#ifndef TBIR_LOG_FATAL_THROW
#define TBIR_LOG_FATAL_THROW 1
#endif

/*!
 * \brief whether always log a message before throw
 * This can help identify the error that cannot be catched.
 */
#ifndef TBIR_LOG_BEFORE_THROW
#define TBIR_LOG_BEFORE_THROW 0
#endif

/*!
 * \brief Whether to use customized logger,
 * whose output can be decided by other libraries.
 */
#ifndef TBIR_LOG_CUSTOMIZE
#define TBIR_LOG_CUSTOMIZE 0
#endif

/*!
 * \brief Whether to enable debug logging feature.
 */
#ifndef TBIR_LOG_DEBUG
#ifdef NDEBUG
#define TBIR_LOG_DEBUG 0
#else
#define TBIR_LOG_DEBUG 1
#endif
#endif

/*!
 * \brief Whether to disable date message on the log.
 */
#ifndef TBIR_LOG_NODATE
#define TBIR_LOG_NODATE 0
#endif

#include <string>
#include <vector>


// align function
#if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__)
#define TBIR_ALIGN_FUNCTION __attribute__((aligned(128)))
#elif defined _MSC_VER
#define TBIR_ALIGN_FUNCTION
#else
#define TBIR_ALIGN_FUNCTION
#endif

// align address
#ifndef TBIR_MEMORY_ALIGNMENT
#define TBIR_MEMORY_ALIGNMENT sizeof(unsigned long) /* platform word */
#endif

#define tbir_memory_align(d, a) (((d) + ((a)-1)) & ~((a)-1))
#define tbir_memory_align_ptr(p, a) \
  (unsigned char*)(((uintptr_t)(p) + ((uintptr_t)(a)-1)) & ~((uintptr_t)(a)-1))

namespace tbir::runtime {

    TURBO_FORCE_INLINE void assume(bool cond) {
#if defined(__clang__)  // Must go first because Clang also defines __GNUC__.
        __builtin_assume(cond);
#elif defined(__GNUC__)
        if (!cond) {
          __builtin_unreachable();
        }
#elif defined(_MSC_VER)
        __assume(cond);
#else
        // Do nothing.
#endif
    }

    TURBO_FORCE_INLINE void assume_unreachable() {
        assume(false);
        // Do a bit more to get the compiler to understand
        // that this function really will never return.
#if defined(__GNUC__)
        __builtin_unreachable();
#elif defined(_MSC_VER)
        __assume(0);
#else
        // Well, it's better than nothing.
        std::abort();
#endif
    }

    /*!
     * \brief safely get the beginning address of a vector
     * \param vec input vector
     * \return beginning address of a vector
     */
    template<typename T>
    inline T *BeginPtr(std::vector<T> &vec) {  // NOLINT(*)
        if (vec.size() == 0) {
            return NULL;
        } else {
            return &vec[0];
        }
    }

    /*!
     * \brief get the beginning address of a const vector
     * \param vec input vector
     * \return beginning address of a vector
     */
    template<typename T>
    inline const T *BeginPtr(const std::vector<T> &vec) {
        if (vec.size() == 0) {
            return NULL;
        } else {
            return &vec[0];
        }
    }

    /*!
     * \brief get the beginning address of a string
     * \param str input string
     * \return beginning address of a string
     */
    inline char *BeginPtr(std::string &str) {  // NOLINT(*)
        if (str.length() == 0)
            return NULL;
        return &str[0];
    }

    /*!
     * \brief get the beginning address of a const string
     * \param str input string
     * \return beginning address of a string
     */
    inline const char *BeginPtr(const std::string &str) {
        if (str.length() == 0)
            return NULL;
        return &str[0];
    }

}  // namespace tbir::runtime
